1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hiprenderer.backend.gl.gltexture;
12 
13 version(OpenGL):
14 public import hip.api.renderer.texture;
15 public import hip.api.data.commons:IReloadable;
16 import hip.hiprenderer.config;
17 
18 import hip.hiprenderer.backend.gl.glrenderer;
19 import hip.error.handler;
20 import hip.image;
21 import hip.math.utils;
22 
23 class Hip_GL3_Texture : IHipTexture, IReloadable
24 {
25     GLuint textureID = 0;
26     int width, height;
27     uint currentSlot;
28 
29     private IImage loadedImage;
30     IHipTexture getBackendHandle(){return this;}
31     bool hasSuccessfullyLoaded(){return width > 0;}
32     protected int getGLWrapMode(TextureWrapMode mode)
33     {
34         switch(mode)
35         {
36             case TextureWrapMode.CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
37             case TextureWrapMode.REPEAT: return GL_REPEAT;
38             case TextureWrapMode.MIRRORED_REPEAT: return GL_MIRRORED_REPEAT;
39             static if(!UseGLES)
40             {
41                 //assert here would be better, as simply returning a default can be misleading.
42                 case TextureWrapMode.MIRRORED_CLAMP_TO_EDGE: return GL_MIRROR_CLAMP_TO_EDGE;
43                 case TextureWrapMode.CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER;
44             }
45             default: return GL_REPEAT;
46         }
47     }
48     protected int getGLMinMagFilter(TextureFilter filter)
49     {
50         switch(filter) with(TextureFilter)
51         {
52             case LINEAR:
53                 return GL_LINEAR;
54             case NEAREST:
55                 return GL_NEAREST;
56             case NEAREST_MIPMAP_NEAREST:
57                 return GL_NEAREST_MIPMAP_NEAREST;
58             case LINEAR_MIPMAP_NEAREST:
59                 return GL_LINEAR_MIPMAP_NEAREST;
60             case NEAREST_MIPMAP_LINEAR:
61                 return GL_NEAREST_MIPMAP_LINEAR;
62             case LINEAR_MIPMAP_LINEAR:
63                 return GL_LINEAR_MIPMAP_LINEAR;
64             default:
65                 return -1;
66         }
67     }
68 
69     private __gshared int globalActiveSlot = 0;
70     ///256 textures should be enough
71     private __gshared Hip_GL3_Texture[256] boundTexture;
72 
73     void bind(int slot = 0)
74     {
75         if(globalActiveSlot != slot)
76         {
77             glCall(() => glActiveTexture(GL_TEXTURE0+slot));
78             globalActiveSlot = slot;
79         }
80         if(boundTexture[globalActiveSlot] !is this)
81         {
82             glCall(() => glBindTexture(GL_TEXTURE_2D, textureID));
83             boundTexture[globalActiveSlot] = this;
84         }
85         currentSlot = slot;
86     }
87 
88     void unbind(int slot = 0)
89     {
90         if(globalActiveSlot != slot)
91         {
92             glCall(() => glActiveTexture(GL_TEXTURE0+slot));
93             globalActiveSlot = slot;
94         }
95         if(boundTexture[globalActiveSlot] is this)
96         {
97             glCall(() => glBindTexture(GL_TEXTURE_2D, 0));
98             boundTexture[globalActiveSlot] = null;
99         }
100         currentSlot = slot;
101     }
102 
103     void setWrapMode(TextureWrapMode mode)
104     {
105         int mod = getGLWrapMode(mode);
106         version(GLES2)
107         {
108             assert((isPowerOf2(width) && isPowerOf2(height)) || mod  == TextureWrapMode.CLAMP_TO_EDGE,
109                 "OpenGL ES 2.0/WebGL 1.0 must use Textures using Power of 2 size. If you wish to use "~
110                 "a non Power of 2, you must use the TextureWrapMode.CLAMP_TO_EDGE"
111             );
112         }
113         bind(currentSlot);
114         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mod));
115         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mod));
116     }
117 
118     void setTextureFilter(TextureFilter min, TextureFilter mag)
119     {
120         int min_filter = getGLMinMagFilter(min);
121         int mag_filter = getGLMinMagFilter(mag);
122         bind(currentSlot);
123         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter));
124         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter));
125     }
126 
127     protected bool loadImpl(in IImage image)
128     {
129         loadedImage = cast(IImage)image;
130         glCall(() => glGenTextures(1, &textureID));
131         if(textureID == 0)
132         {
133             ErrorHandler.assertExit(false, "No texture was generated for image ", image.getName);
134         }
135         int mode;
136         int internalFormat;
137         const(void)[] pixels = image.getPixels;
138         switch(image.getBytesPerPixel)
139         {
140             case 1:
141                 if(image.hasPalette)
142                 {
143                     pixels = image.convertPalettizedToRGBA();
144                     version(GLES20)
145                     {
146                         internalFormat = mode = GL_RGBA;
147                     }
148                     else
149                     {
150                         mode = GL_RGBA;
151                         internalFormat = GL_RGBA8;
152                     }
153                 }
154                 else
155                 {
156                     version(GLES20)
157                     {
158                         internalFormat = mode = GL_LUMINANCE;
159                     }
160                     else
161                     {
162                         mode = GL_RED;
163                         internalFormat = GL_R8;
164                     }
165                 }
166                 break;
167             case 3:
168                 version(GLES20)
169                 {
170                     internalFormat = mode = GL_RGB;
171                 }
172                 else
173                 {
174                     mode = GL_RGB;
175                     internalFormat = GL_RGB8;
176                 }
177                 break;
178             case 4:
179                 mode = GL_RGBA;
180                 internalFormat = GL_RGBA;
181                 break;
182             case 2:
183             default:
184                 import hip.util.conv;
185                 ErrorHandler.assertExit(false, "GL Pixel format unsupported on image "~image.getName~", bytesPerPixel: "~to!string(image.getBytesPerPixel));
186         }
187         width = image.getWidth;
188         height = image.getHeight;
189         bind(currentSlot);
190 
191         glCall(() => glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image.getWidth, image.getHeight, 0, mode, GL_UNSIGNED_BYTE, cast(void*)pixels.ptr));
192         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
193         glCall(() => glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
194         setWrapMode(TextureWrapMode.REPEAT);
195 
196         version(GLES20)
197         if(!isPowerOf2(image.getWidth) || !isPowerOf2(image.getHeight))
198         {
199             setWrapMode(TextureWrapMode.CLAMP_TO_EDGE);
200         }
201         return true;
202     }
203     
204     int getWidth() const {return width;}
205     int getHeight() const {return height;}
206     
207     bool reload()
208     {
209         if(loadedImage !is null)
210         {
211             textureID = 0;
212             return loadImpl(loadedImage);
213         }
214         return false;
215     }
216     
217     
218 }